#include <sstream>
#include <iostream>
#include <iomanip>
#include <ctime>
#include <cstdarg>
#if (defined __linux__) && (!defined ANDROID)
#include <execinfo.h>
#endif
#include "herm/base/container/buffer.h"
#include "log.h"

namespace herm{

LogBase::LogBase()
{
	m_buffer = nullptr;
}

LogBase::~LogBase()
{
	HERM_DELETE(m_buffer);
}

bool LogBase::InitBase(const std::string& dir, const std::string& fileName, int cacheSize)
{
	m_cacheSize = cacheSize;
	if (m_cacheSize != 0)
		m_buffer = new Buffer(cacheSize);
	m_logFile = new std::ofstream;
	std::string path = herm::GetModulePath();
	path += dir;
	path += "//";
	path += fileName;
	path += "_";
	m_filePath = path;
	
	return CreateLogFile();
}

bool LogBase::CreateLogFile()
{
	if (m_logFile->is_open())
	{
		m_logFile->close();
	}
	std::string path = m_filePath;
	path += GetTime2();
	path += ".log";
	m_createTime = GetTime3();
	
	m_logFile->open(path.c_str());
	if (!m_logFile->is_open())
		return false;
	return true;
}

void LogBase::SetHeader(const char* file, int line, const char* func, int level)
{
	m_fileName = file;
	m_fileLine = line;
	m_functionName = func;
	m_tmpLevel = level;
}

bool LogBase::Write(const char* format, ...)
{
	if (m_level > m_tmpLevel)
		return false;

	WriteHeader(m_fileName, m_fileLine, m_functionName, m_tmpLevel);

	char buf[MAX_LOG_SIZE];
	size_t headerSize = m_header.size();
	memcpy(buf, m_header.c_str(), headerSize);

	va_list args;
	va_start(args, format);
	int bytes = vsnprintf(buf + headerSize, sizeof(buf) - headerSize, format, args);
	va_end(args);

	if (bytes == -1)
		return false;

	return WriteBuffer(buf, headerSize + bytes);
}

bool LogBase::Write2Log()
{
	uint16_t len;
	unsigned int bytes = m_buffer->Peek(reinterpret_cast<char*>(&len), sizeof(uint16_t));
	if (bytes != sizeof(uint16_t))
		return false;
	
	// len + string + '\0'
	char buf[sizeof(uint16_t) + MAX_LOG_SIZE + 1];
	len += sizeof(uint16_t);
    if (len > sizeof(buf))
    {
        HERM_ERROR("len[%d] > sizeof(buf)", len);
        len = sizeof(buf);
        m_buffer->Clear();
    }
	bytes = m_buffer->FetchChunk(buf, len);
	if (bytes == 0)
		return false;

	buf[len] = 0;
	if (m_createTime.compare(GetTime3()) != 0)
	{
		CreateLogFile();
	}
	*m_logFile << buf + sizeof(uint16_t) << std::endl;

	return true;
}

bool LogBase::WriteBuffer(const char* buff, unsigned int len)
{
	if (m_cacheSize != 0) 
	{
		if (len > static_cast<unsigned int>(MAX_LOG_SIZE))
			return false;
	
		while (true)
		{
			if (m_buffer->Push(reinterpret_cast<const char*>(&len), sizeof(uint16_t)))
				break;

			herm::Sleep(50);
		}

		while (true)
		{
			if (m_buffer->Push(buff, len))
				break;

			herm::Sleep(50);
		}
	}
	else // The cache buffer is 0, write log directly
		*m_logFile << buff << std::endl;

	return true;
}

std::string LogBase::GetTime()
{
//	HERM_ASSERT(is_open());
	time_t curTime = time(nullptr);
	tm* t = localtime(&curTime);
	std::ostringstream oss;
	oss << t->tm_year + 1900 
		<< std::setfill('0') 
		<< std::setw(2) << t->tm_mon + 1
		<< std::setw(2) << t->tm_mday << ' '
		<< std::setw(2) << t->tm_hour << ':'
		<< std::setw(2) << t->tm_min << ':'
		<< std::setw(2) << t->tm_sec;

	return oss.str();
}

std::string LogBase::GetTime2()
{
	time_t curTime = time(nullptr);
	tm* t = localtime(&curTime);
	std::ostringstream oss;
	oss << t->tm_year + 1900 
		<< std::setfill('0') 
		<< std::setw(2) << t->tm_mon + 1
		<< std::setw(2) << t->tm_mday << "_"
		<< std::setw(2) << t->tm_hour 
		<< std::setw(2) << t->tm_min
		<< std::setw(2) << t->tm_sec;

	return oss.str();
}

std::string LogBase::GetTime3()
{
	time_t curTime = time(nullptr);
	tm* t = localtime(&curTime);
	std::ostringstream oss;
	oss << t->tm_year + 1900 
		<< std::setfill('0') 
		<< std::setw(2) << t->tm_mon + 1
		<< std::setw(2) << t->tm_mday;

	return oss.str();
}

void LogBase::WriteHeader(const char* file, int line, const char* func, int level)
{
	m_header.clear();
	m_header += "[";
	m_header += GetTime();
	m_header += "][";

	switch (level)
	{
	case LL_TRACE:
		m_header += "TRACE";
		break;
	case LL_DEBUG:
		m_header += "DEBUG";
		break;
	case LL_INFO:
		m_header += "INFO";
		break;
	case LL_WARN:
		m_header += "WARNING";
		break;
	case LL_ERROR:
		m_header += "ERROR";
		break;
	case LL_FATAL:
		m_header += "FATAL";
		break;
	default:
		m_header += "UNKNOWN";
		break;
	};
	m_header += "][";

	m_header += file + GetFileNameBegPos(file);
	m_header += ":";
	m_headerOss.str("");
	m_headerOss << line;
	m_header += m_headerOss.str();

	m_header += " ";
	m_header += func;
	m_header += "()] ";
}

int LogBase::GetFileNameBegPos(const char* file)
{
	unsigned char* tmpFile = (unsigned char*)file;
	int pos = 0;
	for (int i = 0; tmpFile[i] != '\0'; i++)
	{
		if (tmpFile[i] == '\\')
			pos = i;
	}

	return pos + 1;
}
} // end namespace herm{

